#include "ServerHandle.h"
#include <cstring>
#include <oatpp-websocket/WebSocket.hpp>
#include <iostream>
#include <fstream>
#include <wx/progdlg.h>
#include <wx/msgdlg.h>
#ifdef _MSC_VER
#define fmt std
#include <format>
#else
#include <fmt/format.h>
#endif


wxDEFINE_EVENT(EVT_SERVER_CONN_NEW,ServerConnEvent);
wxDEFINE_EVENT(EVT_SERVER_CONN_CLOSE,ServerConnEvent);
wxDEFINE_EVENT(EVT_SERVER_NEW_IMAGE,NewImageData);
wxDEFINE_EVENT(EVT_DEVICE_MESSAGE,DeviceMessageEvent);

std::atomic<int> ServerHandle::connectUUID_count=0;

ServerHandle::ServerHandle(wxEvtHandler* EvtHandler,wxWindowID id)
    :_evtHandler(EvtHandler),winID(id)
{}

ConnectHookPtr ServerHandle::CreateNewHook(const oatpp::websocket::WebSocket* socketMemAddress)
{
    std::shared_lock lck(recyclingMut);
    auto [mapPtr,success]=connectMap.try_emplace(connectUUID_count.load(),_evtHandler,winID,connectUUID_count.load(),socketMemAddress);
    //auto [mapPtr,success]=connectMap.insert({connectUUID_count.load(),ConnectHook(_evtHandler,winID,connectUUID_count.load())});
    socketMap.insert({reinterpret_cast<uintptr_t>(mapPtr->second.socketMemAddress),connectUUID_count.load()});
    connectUUID_count++;
    return mapPtr;
}

 void ServerHandle::RecyclingConnect(const oatpp::websocket::WebSocket* socketMemAddress)
 {
    std::lock_guard lck(recyclingMut);
    
    auto socketMapPtr=socketMap.find(reinterpret_cast<uintptr_t>(socketMemAddress));
    if(socketMapPtr==socketMap.end())
        return;
    _evtHandler->QueueEvent(new ServerConnEvent(
        EVT_SERVER_CONN_CLOSE,
        winID,
        socketMapPtr->second,
        std::string()
    ));
    connectMap.erase(socketMapPtr->second);
    socketMap.erase(socketMapPtr);
 }

ConnectHook::ConnectHook(wxEvtHandler* evtHandler,wxWindowID id,long DeviceID,const oatpp::websocket::WebSocket* socketAddress) :
    _evtHandler(evtHandler),winID(id),deviceID(DeviceID),socketMemAddress(socketAddress)
{
}

void ConnectHook::GetNewImage(unsigned char* data,int32_t width,int32_t height,pixelFormat imgFormat)
{
    wxSize size(width,height);
    _evtHandler->QueueEvent(new NewImageData(EVT_SERVER_NEW_IMAGE,winID,deviceID,data,size,imgFormat));
}

void ConnectHook::GetNewReturn(int32_t value)
{
    {
        std::lock_guard lck(getReturnMutex);
        returnValue=value;
    }
    returnDataCond.notify_one();
}

void ConnectHook::SetDeviceName(std::string& data)
{
    std::string ip;
    auto* context=&socketMemAddress->getConnection().object->getInputStreamContext();
    auto res=context->getProperties().get("peer_address");
    if(res==nullptr) ip="";
    else ip=*res;
    _evtHandler->QueueEvent(new ServerConnEvent(
        EVT_SERVER_CONN_NEW,
        winID,
        deviceID,
        data,
        ip
    ));
    deviceName=data;
}

void ConnectHook::SetExposeTime(const int32_t value)
{
    SendControlCommandAndParameter(0x5,(const void*)&value,sizeof(value));
}


void ConnectHook::SetImageHeight(const int32_t value)
{
    SendControlCommandAndParameter(0x2,(const void*)&value,sizeof(value));
}

void ConnectHook::SetImageWidth(const int32_t value)
{
    SendControlCommandAndParameter(0x1,(const void*)&value,sizeof(value));
}

void ConnectHook::SetImageOffsetX(const int32_t value)
{
    SendControlCommandAndParameter(0x3,(const void*)&value,sizeof(value));
}

void ConnectHook::SetImageOffsetY(const int32_t value)
{
    SendControlCommandAndParameter(0x4,(const void*)&value,sizeof(value));
}

void ConnectHook::SetFrameRate(const int32_t value)
{
    SendControlCommandAndParameter(0x6,(const void*)&value,sizeof(value));
}

#define FindDeviceAtMap(deviceID) ConnectHookPtr ptr=connectMap.find(deviceID); \
    if(ptr==connectMap.end()) \
      throw std::length_error("找不到该设备,设备可能已经下线");

void ServerHandle::StartCollegeImage(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StartCollegeImage();
}

void ServerHandle::StopCollegeImage(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StopCollegeImage();
}

int32_t ServerHandle::GetDeviceInfo(const long deviceID)
{

    FindDeviceAtMap(deviceID);
    ptr->second.SendDeviceInfo();
    return ptr->second.GetDeviceInfo();
}

void ServerHandle::InitDevice(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.InitDevice();
}

void ServerHandle::StartShot(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StartShot();
}

void ServerHandle::StopShot(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StopShot();    
}

void ServerHandle::StartCollege(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StartCollege();
}

void ServerHandle::StopCollege(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.StopCollege();
}

void ServerHandle::EnableTriggerMode(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.EnableTrigger();
}

void ServerHandle::DisableTriggerMode(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.DisableTrigger();
}

void ServerHandle::SetExposeTime(const long deviceID,int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetExposeTime(value);
}

CameraParament ServerHandle::GetCameraPara(const long deviceID)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SendCameraParament();
    return ptr->second.GetCameraParament();
}

void ConnectHook::SendControlCommand(int32_t value)
{
    std::string sendData;
    int32_t cc=0x3;
    sendData.append((char*)&cc,sizeof(cc));
    sendData.append((char*)&value,sizeof(value));
    std::lock_guard lck(sendDataMutex);
    socketMemAddress->sendOneFrameBinary(sendData);
    lastSendData=sendData;
}

int32_t ConnectHook::GetControlReturn(int32_t command)
{
    GetDataReady.store(false);
    std::unique_lock lck(getReturnMutex);
    while(true)
    {
        returnDataCond.wait_for(lck,std::chrono::seconds(1));
        if(GetDataReady.load())
            break;
        socketMemAddress->sendOneFrameBinary(lastSendData);
    }
    return returnValue;
}

void ConnectHook::StartCollegeImage()
{
    SendControlCommand(0x1);
}

void ConnectHook::StopCollegeImage()
{
    SendControlCommand(0x2);
}

void ConnectHook::SendDeviceInfo()
{
    SendControlCommand(0x3);
}

int32_t ConnectHook::GetDeviceInfo()
{
    return GetControlReturn(0x3);
}

void ConnectHook::InitDevice()
{
    SendControlCommand(0x4);
}

void ConnectHook::StartShot()
{
    SendControlCommand(0x5);
}

void ConnectHook::StopShot()
{
    SendControlCommand(0x7);
}

void ConnectHook::StartCollege()
{
    SendControlCommand(0x6);
}

void ConnectHook::StopCollege()
{
    SendControlCommand(0x8);
}

void ConnectHook::EnableTrigger()
{
    SendControlCommand(0x10);
}

void ConnectHook::DisableTrigger()
{
    SendControlCommand(0x11);
}

void ConnectHook::SendCameraParament()
{
    SendControlCommand(0x9);
}

void ConnectHook::ReceiveMessage(const std::string value)
{
    _evtHandler->QueueEvent(new DeviceMessageEvent(EVT_DEVICE_MESSAGE,wxID_ANY,value,deviceID));
}

bool ConnectHook::FileTransferFunc(wxWindow* win,const std::string& savePath)
{
    translateStop=deviceTransferReady=false;
#ifdef _MSC_VER
    std::string path = fmt::format("{}\\Device-{}.ccs", savePath, deviceName);
#else
    std::string path=fmt::format("{}/Device-{}.ccs",savePath,deviceName);
#endif // _MSC_VER

    
    oFile.open(path,std::ios_base::trunc | std::ios_base::binary);
    if(!oFile.is_open())
    {
        wxMessageBox(wxT("无法正确保存文件"),wxT("错误"),wxOK | wxICON_ERROR);
        return false;
    }
    SendFileTranslateCommand(0x2);
    GetFileTranslateCommand();
    if(deviceTransferReady==false)
    {
        wxMessageBox(wxT("设备传输准备失败"),wxT("错误"),wxOK | wxICON_ERROR);
        return false;
    }
    receiveMsg.reset(new ReveiceDeviceData(&oFile,win,frameCount));
    SendFileTranslateCommand(0x3);  //开始传输
    /*
    wxProgressDialog prog(wxT("文件传输"),wxString().FromUTF8(fmt::format("文件传输中，当前存档保存路径为:\n{}",path)),frameCount,win,wxPD_SMOOTH);
    SendFileTranslateCommand(0x3);
    std::unique_lock lck(getFileTransferMutex);
    for(int i=0;i<frameCount;i++)
    {
        returnFileTransferCond.wait(lck,[this]{return fileDataReady.load();});
        fileDataReady.store(false);
        if(translateStop)
        {
            wxMessageBox(wxT("当前传输已被设备终止"),wxT("错误"),wxOK | wxICON_ERROR,win);
            return false;
        }
        if(frameIndex!=i) //说明当前序号不等于接收序号
        {
            SendFileTranslateCommand(0x4);
            wxMessageBox(wxT("传输数据块错误，当前传输已停止"),wxT("错误"),wxOK | wxICON_ERROR,win);
            return false;
        }
        prog.Update(i);
        oFile.write(dataBuff,512);
    }
    oFile.close();
    SendFileTranslateCommand(0x4);
    */
    return true;
}

void ConnectHook::SendFileTranslateCommand(const int32_t& value)
{
    std::string sendData;
    int32_t cc=0x9;
    sendData.append((char*)&cc,sizeof(cc));
    sendData.append((char*)&value,sizeof(value));
    std::lock_guard lck(sendDataMutex);
    socketMemAddress->sendOneFrameBinary(sendData);
}

void ConnectHook::GetFileTranslateCommand()
{
    std::unique_lock lck(getFileTransferMutex);
    returnFileTransferCond.wait(lck,[this]{return fileDataReady.load();});
    fileDataReady.store(false);
}

void ConnectHook::ReceiveFileData()
{
    std::unique_lock lck(getFileTransferMutex);
    returnFileTransferCond.wait(lck,[this]{return fileDataReady.load();});
    fileDataReady.store(false);
}

void ConnectHook::HandleFileTransfer(const char* data,const size_t size)
{
    int32_t command;
    std::memcpy((char*)&command,data,sizeof(int32_t));
    switch (command)
    {
    case 0x1: //返回值
        {
            int32_t returnValue;
            {
                std::lock_guard lck(getFileTransferMutex);
                std::memcpy((char*)&returnValue,data+sizeof(int32_t),sizeof(int32_t));
                if(returnValue==0x1)
                {
                    std::memcpy((char*)&frameCount,data+sizeof(int32_t)*2,sizeof(int64_t));
                    deviceTransferReady=true;
                }
                else
                    deviceTransferReady=false;
                fileDataReady.store(true);
            }
            returnFileTransferCond.notify_one();
        }
        break;
    case 0x4:  //传输停止
        {
            std::lock_guard lck(getFileTransferMutex);
            translateStop=true;
            returnFileTransferCond.notify_all();
        }
        break;
    case 0x5:  //数据分片
        {
            if(receiveMsg==nullptr) return;
            int64_t frameNum;
            std::memcpy((char*)&frameNum,data+sizeof(int32_t),sizeof(int64_t));
            if(frameNum==frameCount)
            {
                receiveMsg.reset(nullptr);
                SendFileTranslateCommand(0x4);
                return;
            }
            if((*receiveMsg)(frameNum,data+sizeof(int32_t)*3)==false)
            {
                SendFileTranslateCommand(0x4);
                wxMessageBox(wxT("文件接收错误"),wxT("文件传输"),wxICON_ERROR | wxOK,dynamic_cast<wxWindow*>(_evtHandler));
            }
        }
        break;
    default:
        break;
    }
}

CameraParament ConnectHook::GetCameraParament()
{
    std::unique_lock lck(getCameraParaMutex);
    returnCameraParaCond.wait(lck);
    return returnCameraPara;
}

void ConnectHook::GetCameraParaReturn(char* cameraPara)
{
    std::lock_guard lck(getCameraParaMutex);
    std::memcpy((char*)&returnCameraPara,cameraPara,sizeof(CameraParament));
    returnCameraParaCond.notify_one();
}

const int32_t sendControlCommandAndParameterTag=0x8;
void ConnectHook::SendControlCommandAndParameter(int32_t value,const void* para,size_t psize)
{
    std::string sendData;
    sendData.append((const char*)&sendControlCommandAndParameterTag,sizeof(int32_t));
    sendData.append((const char*)&value,sizeof(int32_t));
    sendData.append((const char*)para,psize);
    std::lock_guard lck(sendDataMutex);
    socketMemAddress->sendOneFrameBinary(sendData);
}

void ServerHandle::SetImageHeight(const long deviceID,const int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetImageHeight(value);
}

void ServerHandle::SetImageWidth(const long deviceID,const int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetImageWidth(value);
}

void ServerHandle::SetImageOffsetX(const long deviceID,const int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetImageOffsetX(value);
}

void ServerHandle::SetImageOffsetY(const long deviceID,const int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetImageOffsetY(value);
}

void ServerHandle::SetFrameRate(const long deviceID,const int32_t value)
{
    FindDeviceAtMap(deviceID);
    ptr->second.SetFrameRate(value);
}

bool ServerHandle::FileTransferFunc(const long deviceID,const std::string& savePath)
{
    FindDeviceAtMap(deviceID);
    return ptr->second.FileTransferFunc(dynamic_cast<wxWindow*>(_evtHandler),savePath);
}

void ConnectHook::SetGetDataReady(bool value)
{
    GetDataReady=true;
}
